﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Nomad.ASPMVVM {
    [Serializable]
    public class PageView : Page {
        private readonly IDictionary<Control, BindingProperty[]> bindingProperties = new Dictionary<Control, BindingProperty[]>();

        private object ViewModelContext {
            get { return Context.Items["ViewModelState"]; }
            set {
                Context.Items.Remove("ViewModelState");
                Context.Items.Add("ViewModelState", value);
            }
        }

        private string _viewModel;

        public string ViewModel {
            get {
                return _viewModel;
            }
            set {
                _viewModel = value;
            }
        }

        protected override void OnInit(EventArgs e) {
            DataBind();
            if (!IsPostBack) InitializeBindingProperties(this);

            base.OnInit(e);
        }
        protected override void OnLoad(EventArgs e) {
            InitializeViewModel();
            if (IsPostBack) InitializeBindingProperties(this);
            base.OnLoad(e);
        }

        private void InitializeViewModel() {
            if (ViewModelContext == null && !String.IsNullOrWhiteSpace(this.ViewModel)) {
                var viewModelAssemblyName = this.ViewModel;
                var assmebly = Assembly.LoadWithPartialName(viewModelAssemblyName);
                while (assmebly == null) {
                    if (viewModelAssemblyName.LastIndexOf('.') <= 0) break;
                    viewModelAssemblyName = viewModelAssemblyName.Remove(viewModelAssemblyName.LastIndexOf('.'));
                    assmebly = Assembly.LoadWithPartialName(viewModelAssemblyName);
                }

                if (assmebly == null) return;

                var vm = assmebly.CreateInstanceOf<PageViewModel>(this.ViewModel);
                if (vm != null) {
                    vm.PropertyChanged += ViewModelPropertyChanged;
                }
                this.ViewModelContext = vm;

                if (!IsPostBack) vm.InitializeProperties();
            }
        }
        private void InitializeBindingProperties(Control control) {
            if (control is IAttributeAccessor) {
                var value = (control as IAttributeAccessor).GetAttribute("Binding");
                if (value != null) {
                    var bindings = BindingProperty.TryParse(value);
                    if (bindings != null) {
                        var query = bindings.Where(bp => bp.IsCommand.GetValueOrDefault(false) == false);
                        if (query.Any() == true)
                            bindingProperties.Add(control, query.ToArray());

                        foreach (var binding in bindings) {
                            if (binding.IsCommand.GetValueOrDefault(false)) {
                                AttachCommand(control, binding);
                            } else {
                                AttachProperty(control, binding);
                            }
                        }
                    }
                }
            }

            control.Controls.OfType<Control>().ToList().ForEach(item => InitializeBindingProperties(item));
        }

        private void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e) {
            this.ApplyBindings(e.PropertyName);
        }
        public void ActionCommand(object sender, CommandEventArgs e) {
            this.ApplyCommand(e.CommandName);
        }

        private void AttachCommand(Control control, BindingProperty bp) {
            if (ViewModelContext == null || control == null || bp == null) return;

            var viewModelProperty = ViewModelContext.GetType().GetProperty(bp.Path);
            var controlCommandEvent = control.GetType().GetEvents().FirstOrDefault(ev => ev.EventHandlerType == typeof(CommandEventHandler));
            if (controlCommandEvent != null && viewModelProperty != null) {
                var command = viewModelProperty.GetValue(ViewModelContext);
                if (command is ICommand) {
                    var commandEventHandler = new CommandEventHandler(delegate(object sender, CommandEventArgs e) {
                        (command as ICommand).Execute(e.CommandArgument);
                    });

                    controlCommandEvent.AddEventHandler(control, commandEventHandler);
                }
            }
        }
        private void AttachProperty(Control control, BindingProperty bp) {
            if (ViewModelContext == null || control == null || bp == null) return;

            var controlProperty = control.GetType().GetProperty(bp.Property);
            var viewModelProperty = ViewModelContext.GetType().GetProperty(bp.Path);
            if (controlProperty != null && viewModelProperty != null) {
                var value = controlProperty.GetValue(control);
                var valueType = viewModelProperty.PropertyType;
                try {
                    viewModelProperty.SetValue(ViewModelContext, Convert.ChangeType(value, valueType));
                } catch {
                    var defaultValue = valueType.Assembly.CreateInstance(valueType.FullName);
                    viewModelProperty.SetValue(ViewModelContext, defaultValue);                    
                }
            }
        }
        
        private void ApplyBindings(string propertyName) {
            if (ViewModelContext == null) return;
            if (bindingProperties.Any(item => item.Value.Any(var => var.Path == propertyName)) != true) return;

            var bindingsControl = bindingProperties.FirstOrDefault(item => item.Value.Any(var => var.Path == propertyName));

            var control = bindingsControl.Key;
            var binding = bindingsControl.Value.FirstOrDefault(item => item.Path == propertyName);

            var controlProperty = control.GetType().GetProperty(binding.Property);
            var viewModelProperty = ViewModelContext.GetType().GetProperty(binding.Path);

            if (controlProperty != null && viewModelProperty != null) {
                var value = viewModelProperty.GetValue(ViewModelContext);
                if (value is IEnumerable)
                    controlProperty.SetValue(control, value);
                else if (controlProperty.PropertyType == typeof(String))
                    controlProperty.SetValue(control, String.Format(binding.Format ?? "{0}", value));
                else
                    controlProperty.SetValue(control, value);

                if (binding.NeedsBind.GetValueOrDefault(false)) control.DataBind();
            }
        }
        private void ApplyCommand(string commandName) {
            if (ViewModelContext != null) {
                var method = ViewModelContext.GetType().GetMethod(commandName);
                if (method != null) {
                    method.Invoke(ViewModelContext, new object[0]);
                }
            }
        }

        public string Eval(params BindingProperty[] values) {
            return BindingProperty.Serialize(values);
        }
    }
}
